Ownership
- Ownership enables Ruts to make memory safety guarantees without needing a garbage collector. There are several related features that help accomplish this:
borrowing
,slices
Function
- The mechanics of passing a value to a function are similar to those when assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment does.
fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it's okay to still
// use x afterward
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
- If we tried to use
s
after the call totakes_ownership
, Rust would throw a compile-time error.
Return Value and Scope
- Returning values can also transfer ownership.
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.
fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it
let some_string = String::from("yours"); // some_string comes into scope
some_string // some_string is returned and
// moves out to the calling
// function
}
// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope
a_string // a_string is returned and moves out to the calling function
}
- What if we want to let a function use a value but not take ownership => Anything we pass in also needs to be passed back if we want to use it again, in addition to any resulting data => Returns multiple values as a tuple
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String
(s, length)
}
- These are all too much and a lot of work for a concept that should be common => Use [[Ownership#References and Borrowing | References]] instead
References and Borrowing
- The current issue with returning the tuple code is that we have to return the passed in value to the calling function so we can still use the original value as the function already take ownership of the variable/the value was moved into the function.
- Instead, we can provide a References to the passed in value. A references is like a pointer in that it's an address we can follow to access the data stored at that address, that data is owned by some other variable. Unlike pointer, a reference is guaranteed to point to a valid value of a particular type for the life of that reference.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what // it refers to, it is not dropped.
- The ampersands represent References, and they allow you to refer to some value without taking ownership of it.
- The scope in which
s
is valid is the same as any function parameter's scope, but the value pointed to by the reference is not dropped whens
stops being used ass
does not have ownership. - The action of creating a References is borrowing. As in real life, if a person owns something, you can borrow it from them and you have to give it back when you are done, you don't own it.
- Similar to variables, References are also immutable by default.
Mutable References
- If we actively want to modify a value using a function, we can use Mutable References
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
- This make very clear that the function will mutate the value it borrows.
- If you have mutable reference to a value, you can have no other references to that value until that mutable references has been used.
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
// THIS WILL NOT COMPILE
- The first mutable borrow is in
r1
and must last until it’s used in theprintln!
, but between the creation of that mutable reference and its usage, we tried to create another mutable reference inr2
that borrows the same data asr1
. - This prevents Data Races => Rust prevents this problem in compile time.
- Two or more pointers access the same data at the same time.
- At least one of the pointers is being used to write to the data.
- There’s no mechanism being used to synchronize access to the data
- We can allow multiple mutable references, just not simultaneous one
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
- Rust enforces a similar rule for combining mutable and immutable references.
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
// THIS WILL NOT COMPILE
- Users of an immutable reference don’t expect the value to suddenly change out from under them!
- However, multiple immutable references are allowed because no one who is just reading the data has the ability to affect anyone else’s reading of the data.
- This works as the last usage of the immutable references occurs before the mutable reference is introduced
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
Dangling References
- In languages with pointers, it's easy to create a Dangling pointer - a pointer that references a location in memory that may have been given to someone else.
- Rust guarantees that references will never be dangling references:
If you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does
fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
- Because
s
is created insidedangle
, when the code ofdangle
is finished,s
will be deallocated. But we tried to return a reference to it. That means this reference would be pointing to an invalidString
.